/**************************************************************************
 * This file is property of and copyright by the ALICE HLT Project        *
 * All rights reserved.                                                   *
 *                                                                        *
 * Primary Authors:                                                       *
 *   Artur Szostak <artursz@iafrica.com>                                  *
 *                                                                        *
 * Permission to use, copy, modify and distribute this software and its   *
 * documentation strictly for non-commercial purposes is hereby granted   *
 * without fee, provided that the above copyright notice appears in all   *
 * copies and that both the copyright notice and this permission notice   *
 * appear in the supporting documentation. The authors make no claims     *
 * about the suitability of this software for any purpose. It is          *
 * provided "as is" without express or implied warranty.                  *
 **************************************************************************/

// $Id: $

/**
 * \ingroup macros
 * \file SanityCheckGlobalTriggerDecisions.C
 * \brief Macro for performing sanity checks on the HLT global trigger decisions.
 *
 * Basic sanity checks are performed by the SanityCheckGlobalTriggerDecisions macro
 * on the HLT global trigger counters. These checks include:
 *  - The counters must be increasing with increasing event ID.
 *  - The total events counter must be the largest and the number of events read
 *    by the raw reader must be less than or equal to the total events counter.
 *
 * The simplest way to run this macro with defaults is to run the following
 * command from a terminal shell:
 * \code
 *   > aliroot -b -q $ALICE_ROOT/HLT/trigger/macros/SanityCheckGlobalTriggerDecisions.C
 * \endcode
 * This will expect data in DDL directory format in the current directory.
 *
 * \author Artur Szostak <artursz@iafrica.com>
 */

#if !defined(__CINT__) || defined(__MAKECINT__)
#include "AliHLTGlobalTriggerDecision.h"
#include "TSystem.h"
#include "TString.h"
#include "TFile.h"
#include "TCollection.h"
#include "TMap.h"
#include "TObjArray.h"
#include "TObjString.h"
#include "TArrayI.h"
#include "TArrayL64.h"
#include "Riostream.h"
#endif

#include "DumpGlobalTrigger.C"


/**
 * Checks to see if a file contains global HLT trigger decision objects.
 * \param filename  The name of the ROOT file to check.
 * \returns true if the file contains AliHLTGlobalTriggerDecision objects and
 *    false otherwise.
 */
bool FileContainsDecisions(const char* filename)
{
	TFile* file = new TFile(filename, "READ");
	if (file == NULL)
	{
		cerr << "ERROR: Could not create a TFile object to open '"
			<< filename << "'." << endl;
		return false;
	}
	TIter next(file->GetListOfKeys());
	TObject* key = NULL;
	bool result = false;
	while ((key = next()) != NULL)
	{
		TObject* obj = file->Get(key->GetName());
		if (obj == NULL) continue;
		if (TString(obj->ClassName()) == "AliHLTGlobalTriggerDecision")
		{
			result = true;
			break;
		}
	}
	delete file;
	return result;
}

/**
 * Routine to print the counters in a HLT global trigger decision object.
 * \param key  The key object storing the key name of the object in the ROOT file.
 * \param decision  The HLT global decision object to print.
 */
void PrintCounters(const TObject* key, const AliHLTGlobalTriggerDecision* decision)
{
	if (key == NULL) return;
	if (decision == NULL) return;
	cout << key->GetName() << " (Global component ID = " << decision->GetUniqueID() << "):";
	const TArrayL64& counters = decision->Counters();
	for (Int_t i = 0; i < counters.GetSize(); ++i)
	{
		cout << " " << counters[i];
	}
	if (counters.GetSize() <= 0) cout << "(none)";
	cout << endl;
}

/**
 * Performs sanity checks on the global HLT trigger counters found in the raw data.
 *
 * \param dataSource  This is the path to the raw data or the ROOT/DATE file
 *     contining the raw data. (default is the current directory).
 *     One can also specify the output ROOT file as generated by the
 *     DumpGlobalTrigger.C macro directly.
 * \param firstEvent  The event number of the first event to process. (default = 0)
 *     This parameter is ignored if dataSource points to a file containing
 *     AliHLTGlobalTriggerDecision objects.
 * \param lastEvent  The event number of the last event to process. If this is
 *     less than firstEvent then it is set to maximum events available
 *     automatically. (default = -1)
 *     This parameter is ignored if dataSource points to a file containing
 *     AliHLTGlobalTriggerDecision objects.
 * \param debug  Specifies if full debug messages should be printed when running
 *     the DumpGlobalTrigger.C macro.
 * \returns true if the data passed all validity checks and false if there was a problem.
 */
bool SanityCheckGlobalTriggerDecisions(
		const char* dataSource = "./",
		Int_t firstEvent = 0,
		Int_t lastEvent = -1,
		bool debug = false
	)
{
	const char* outputFile = "globalTriggerDecisions.root";
	if ((! TString(dataSource).EndsWith(".root")) ||
	    (TString(dataSource).EndsWith(".root") && ! FileContainsDecisions(dataSource))
	   )
	{
		if (gSystem->Exec(Form("test -f %s", outputFile)) == 0)
		{
			cerr << "ERROR: File " << outputFile
				<< " already exists. It must be moved or removed."
				<< " This script will not overwrite it."
				<< endl;
			return false;
		}
		DumpGlobalTrigger(dataSource, firstEvent, lastEvent, outputFile, debug);
	}
	else
	{
		outputFile = dataSource;
	}

	TMap map;
	map.SetOwnerKeyValue(kTRUE, kTRUE);
	TObjArray eventIds;
	eventIds.SetOwner(kFALSE);
	TArrayI objIds;

	TFile* file = new TFile(outputFile, "READ");
	if (file == NULL)
	{
		cerr << "ERROR: Could not create a TFile object to open '"
			<< outputFile << "'." << endl;
		return false;
	}
	TIter next(file->GetListOfKeys());
	TObject* key = NULL;
	while ((key = next()) != NULL)
	{
		TObject* obj = file->Get(key->GetName());
		if (obj == NULL)
		{
			cerr << "Warning: Could not fetch object '" << key->GetName() << "'." << endl;
			continue;
		}
		TObjString* id = new TObjString(key->GetName());
		map.Add(id, obj->Clone());
		eventIds.Add(id);
		bool addID = true;
		for (Int_t i = 0; i < objIds.GetSize(); ++i)
		{
			if (objIds[i] == Int_t(obj->GetUniqueID()))
			{
				addID = false;
				break;
			}
		}
		if (addID)
		{
			objIds.Set(objIds.GetSize()+1);
			objIds[objIds.GetSize()-1] = Int_t(obj->GetUniqueID());
		}
	}
	delete file;

	eventIds.Sort();  // Must sort the events in order of increasing event ID.
	ULong64_t totalCounters = 0;
	bool result = true;

	for (Int_t n = 0; n < objIds.GetSize(); ++n)
	{
		UInt_t objId = UInt_t(objIds[n]);
		TObject* oldKey = NULL;
		AliHLTGlobalTriggerDecision* oldDecision = NULL;
		TIter nextEvent(&eventIds);
		AliHLTGlobalTriggerDecision* decision = NULL;
		while ((key = nextEvent()) != NULL)
		{
			TPair* pair = (TPair*) map.FindObject(key->GetName());
			if (pair == NULL)
			{
				cerr << "Warning: Could not find trigger decision '" << key->GetName() << "' in the map." << endl;
				continue;
			}
			decision = (AliHLTGlobalTriggerDecision*) pair->Value();
			if (decision == NULL)
			{
				cerr << "Warning: The trigger decision object for '" << key->GetName() << "' was NULL in the map." << endl;
				continue;
			}
			if (decision->GetUniqueID() != objId) continue;  // Filter on the current ID we are handling.

			bool printCounters = false;
			const TArrayL64& counters = decision->Counters();

			// Check that all current counters are larger than the old ones.
			if (oldDecision != NULL)
			{
				const TArrayL64& oldCounters = oldDecision->Counters();
				if (counters.GetSize() == oldCounters.GetSize())
				{
					for (Int_t i = 0; i < counters.GetSize(); ++i)
					{
						if (oldCounters[i] > counters[i])
						{
							cerr << "ERROR: Previous counter value " << oldCounters[i]
								<< " from object " << oldKey->GetName()
								<< " is larger than the new one " << counters[i]
								<< " from " << key->GetName()
								<< "." << endl;
							printCounters = true;
							result = false;
						}
					}
				}
				else
				{
					cerr << "ERROR: Number of previous counters from object " << oldKey->GetName()
						<< " do not match the current counters from " << key->GetName()
						<< "." << endl;
					printCounters = true;
					result = false;
				}
			}

			// Check that all counters are larger than the total counter at the end.
			if (counters.GetSize() > 0)
			{
				for (Int_t i = 0; i < counters.GetSize()-1; ++i)
				{
					if (counters[i] > counters[counters.GetSize()-1])
					{
						cerr << "ERROR: Counter " << i
							<< " with value " << counters[i]
							<< " from object " << oldKey->GetName()
							<< " is larger than the last counter " << counters[counters.GetSize()-1]
							<< "." << endl;
						printCounters = true;
						result = false;
					}
				}
			}

			if (debug && ! printCounters) PrintCounters(key, decision);
			if (printCounters)
			{				
				cout << "Previous counters: "; PrintCounters(oldKey, oldDecision);
				cout << " Current counters: "; PrintCounters(key, decision);
				cout << endl;
			}
			oldDecision = decision;
			oldKey = key;
		}
		if (oldDecision != NULL)
		{
			if (oldDecision->Counters().GetSize() > 0)
			{
				// oldDecision will contain the last counter, which we use to get
				// the total number of events seen by the global trigger component.
				const TArrayL64& oldCounters = oldDecision->Counters();
				totalCounters += oldCounters[oldCounters.GetSize()-1];
			}
		}
	}
	
	if (ULong64_t(eventIds.GetEntries()) > totalCounters)
	{
		cerr << "ERROR: The total number of events counters added up to " << totalCounters
			<< ", but the total number of events seen on file is " << eventIds.GetEntries()
			<< ". This is inconsistent." << endl;
		result = false;
	}
	return result;
}

